/*
 * Alu.java
 * 
 * Team Dovahkiin:
 * Major Bruno, Matthew Selfors
 * 
 * This class is a simulation of the Arithmetic Logic Unit in the SC-2.
 */

package phaseI;

import java.util.Random;

/**
 * This class is a simulation of the Arithmetic Logic Unit in the SC-2.
 * 
 * @author Major Bruno
 * @author Matthew Selfors
 * @version 1.0
 */
public class Alu {

	/**
	 * The three opcodes used by the SC-2.
	 */
	private static final int MATH = 1001, SHL = 1000, SHR = 1010;
	
	/**
	 * A base and exponent for binary calculations.
	 */
	private static final int BASE = 2, EXP = 15;
	
	/**
	 * The maximum random value for the opcode field.
	 */
	private static final int RAND_SIZE = 32;

	/**
	 * The various modifiers for instructions sharing the same opcode.
	 */
	private static final int ADD = 0, SUB = 1, MUL = 10, DIV = 11,
			AND = 100, OR = 101, XOR = 110, NOT = 111;

	/**
	 * The four registers used for ALU operations.
	 */
	protected Register RegA, RegB, RegR, RegR2;

	/**
	 * The status word register.
	 */
	protected Register sw;

	/**
	 * The status flags; negative, zero, carry and overflow respectively.
	 */
	protected int n, z, c, o;

	/**
	 * The opcode of the current instruction.
	 */
	protected int opcode;

	/**
	 * the modifier of the instruction's opcode.
	 */
	protected int modifier;
	
	/**
	 * Integer version of RegA used for calculations.
	 */
	protected int rega;
	
	/**
	 * Integer version of RegB used for calculations.
	 */
	protected int regb;

	/**
	 * Calls super() and initializes the fields for the class.
	 */
	public Alu(final String[] values) {
		super();
		Random rand = new Random();
		int count = 0;
		RegA = new Register(values[count]);
		count++;
		RegB = new Register(values[count]);
		count++;
		RegR = new Register(values[count]);
		count++;
		RegR2 = new Register(values[count]);
		count++;
		sw = new Register(values[count]);

		n = z = c = o = 0;

		opcode = rand.nextInt(RAND_SIZE);
		
		rega = decifyTheBinary(RegA.getRegValue());
		regb = decifyTheBinary(RegB.getRegValue());
	}

	/**
	 * Sets the value of the A operand register.
	 * 
	 * @param reg the value
	 */
	public void setRegA(final Register reg) {
		RegA = reg;
		rega = decifyTheBinary(reg.getRegValue());
	}

	/**
	 * Sets the value of the B operand register.
	 * 
	 * @param reg the value
	 */
	public void setRegB(final Register reg) {
		RegB = reg;
		regb = decifyTheBinary(reg.getRegValue());
	}

	/**
	 * Gets the A operand register.
	 * 
	 * @return the register
	 */
	public Register getRegA() {
		return RegA;
	}

	/**
	 * Gets the B operand register.
	 * 
	 * @return the register
	 */
	public Register getRegB() {
		return RegB;
	}

	/**
	 * Gets the result register.
	 * 
	 * @return the register
	 */
	public Register getRegR() {
		return RegR;
	}

	/**
	 * Gets the result 2 register.
	 * 
	 * @return the register
	 */
	public Register getRegR2() {
		return RegR2;
	}

	/**
	 * Allows a CPU object to set the Status Word for the ALU to use.
	 * 
	 * @param newStatus the new Status word.
	 */
	public void setSW() {
		sw = new Register(n + (z + (c + (o + "000000000000"))));
	}
	
	public Register getSW() {
		return sw;
	}

	/**
	 * Sets all four Status Flags to zero.
	 */
	protected void clearFlags() {
		n = z = c = o = 0;
	}

	/**
	 * Sets the four Status Flags based on the last ALU operation.
	 * 
	 * If op_type is 0, a single operand instruction is assumed. If 1, the
	 * operation is add, If 2, the operation is sub. If 3, it is any other 2
	 * operand instruction.
	 * 
	 * @param op_type a flag indicating the number of operands in the ALU operation.
	 */
	public void setFlags(int op_type, int value) {
		int regr = decifyTheBinary(RegR.getRegValue());
		
		if (regr < 0) {
			n = 1;
		} else if (regr == 0) {
			z = 1;
		}

		// test the value of op_type
		if (op_type == 0) {
			if (value > Math.pow(BASE, EXP)) {
				c = 1;
				if (RegR.signOf() == 0) {
					o = 1;
				}
			}
			if (RegR.signOf() == 1 && value > 0) {
				o = 1;
			}
		} else if (op_type == 1) {
			if (RegA.signOf() == RegB.signOf()
					&& RegR.signOf() != RegA.signOf()) {
				o = 1;
			}
			if (RegA.signOf() != RegB.signOf()
					|| (RegA.signOf() == 1 && RegB.signOf() == 1)) {
				if (value < -Math.pow(BASE, EXP) || value > Math.pow(BASE, EXP)) {
					c = 1;
				}
			}
		} else if (op_type == 2) {
			if ((rega > 0 && regb < 0)
					&& (value > Math.pow(BASE, EXP))) {
				o = 1;
			}
			if ((rega < 0 && regb > 0)
					&& (value < -Math.pow(BASE, EXP))) {
				o = 1;
			}
			if (RegA.signOf() == 1 && RegB.signOf() == 0) {
				if (value < -Math.pow(BASE, EXP) || value > Math.pow(BASE, EXP)) {
					c = 1;
				}
			}
		} else if (op_type == 3) {
			if (value < -Math.pow(BASE, EXP)) {
				c = 1;
				if (RegR.signOf() == 0) {
					o = 1;
				}
			} else if (value > Math.pow(BASE, EXP)) {
				c = 1;
				if (RegR.signOf() == 1) {
					o = 1;
				}
			}
		}
		
		setSW();
	}

	/**
	 * Sets the current opcode for the ALU operation.
	 * 
	 * @param newOpcode the new opcode.
	 */
	public void setOperation(final int newOpcode) {
		opcode = newOpcode;
	}

	/**
	 * Sets the current modifier for the ALU operation.
	 * 
	 * @param newModifier the new modifier.
	 */
	public void setModifier(final int newModifier) {
		modifier = newModifier;
	}

	/**
	 * Executes an ALU operation based on the current opcode and modifier,
	 * using RegA and RegB for the calculation, storing the result in RegR
	 * and RegR2 if applicable.
	 */
	public void executeOperation() {
		clearFlags();
		switch (opcode) {
		case MATH:
			switch (modifier) {
			case ADD:
				add();
				break;

			case SUB:
				sub();
				break;

			case MUL:
				mul();
				break;

			case DIV:
				div();
				break;

			case AND:
				and();
				break;

			case OR:
				or();
				break;

			case XOR:
				xor();
				break;

			case NOT:
				not();
				break;
			}
			break;
		case SHL:
			shl();
			break;

		case SHR:
			shr();
			break;
		}
	}

	/**
	 * Adds two values together.
	 */
	private void add() {
		int result = rega + regb;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(1, result);

	}

	/**
	 * Subtracts one value from another.
	 */
	private void sub() {
		int result = rega - regb;;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(2, result);
	}

	/**
	 * Multiplies two values together.
	 */
	private void mul() {
		int result = rega * regb;
		String binary = binarizeTheDecimal(result);
		String tempRegR = "";
		String tempRegR2 = "";
		if (binary.length() <= 16) {
			tempRegR2 = binary;
		} else {
			for (int i = binary.length() - 1; i >= 0; i--) {
				if (i > binary.length() - 16) {
					tempRegR = binary.charAt(i) + tempRegR;
				} else {
					tempRegR2 = binary.charAt(i) + tempRegR;
				}
			}
		}
		RegR.setRegValue(tempRegR);
		RegR2.setRegValue(tempRegR2);
		setFlags(3, result);
	}

	/**
	 * Divides one value from another.
	 */
	private void div() {
		int result = rega / regb;
		RegR.setRegValue(binarizeTheDecimal(result));
		RegR2.setRegValue(binarizeTheDecimal(rega % regb));
		setFlags(3, result);
	}

	/**
	 * Applies a bitwise AND to two values.
	 */
	private void and() {
		int result = rega & regb;;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(3, result);
	}

	/**
	 * Applies a bitwise OR to two values.
	 */
	private void or() {
		int result = rega | regb;;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(3, result);
	}

	/**
	 * Applies a bitwise XOR to two values.
	 */
	private void xor() {
		int result = rega ^ regb;;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(3, result);
	}

	/**
	 * Applies a bitwise NOT to a value.
	 */
	private void not() {
		int result = ~rega;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(0, result);
	}

	/**
	 * Applies a bitwise left shift to a value.
	 */
	private void shl() {
		int result = rega << 1;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(0, result);
	}

	/**
	 * Applies a bitwise right shift to a value.
	 */
	private void shr() {
		int result = rega >> 1;
		RegR.setRegValue(binarizeTheDecimal(result));
		setFlags(0, result);
	}
	
	/**
	 * Converts a binary string to an integer.
	 * 
	 * @param value the binary string.
	 * @return the integer.
	 */
	public int decifyTheBinary(final String value) {
		int final_value = 0;
		boolean neg = false;
		String temp_string = value;

		if (value.charAt(0) == '1') {
			neg = true;
			for (int i = 0; i < value.length(); i++) {
				if (value.charAt(i) == '1') {
					temp_string += "0";
				} else {
					temp_string += "1";
				}
			}
		}

		for (int i = temp_string.length() - 1; i > 0; i--) {
			if (temp_string.charAt(i) == '1') {
				final_value += Math.pow(BASE, EXP - i);
			}
		}

		if (neg) {
			final_value++;
			final_value = -final_value;
		}

		return final_value;
	}
	
	/**
	 * Converts an integer to a binary string.
	 * 
	 * @param value the integer.
	 * @return the binary string.
	 */
	public String binarizeTheDecimal(final int value) {
		if (value < 0) {
			return Integer.toBinaryString(value).substring(16);
		} else {
			return Integer.toBinaryString(value);
		}
	}
}
